#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##-- G06 -- S05 ------------------------------------------------------------##
__all__ = []

from .. import autostacks
from .. import comparecards as cmp_cards
from .. import pilelib as stack
from .. import settings as c
from ..formlib import Layout
from ..gamelib import info as gi, state, game
from ..hint import AbstractHint
from ..packlib import place
from ..pilelib import move
from ..piles import pairing

GT, SL = gi.GT, gi.SL
class PushPin_Hint(AbstractHint):

    def compute_hints(self):
        """
        compute_hints() -> None

        compute hints - main hint intelligence
        """
        rows = self._stacks['set']['rows']
        foundation = self._stacks['set']['foundations'][0]
        for i in range(len(rows) - 3):
            stacks = [rows[i + 1]]
            if rows[i + 2].cards and stack._cardPairing(*stacks):
                self.add_hint(5000, 1, stacks[0], foundation)
            stacks += [rows[i + 2]]
            if rows[i + 3].cards and stack._cardPairing(*stacks):
                self.add_hint(6000, 1, stacks[0], stacks[1])


class Accordion_Hint(AbstractHint):

    def compute_hints(self):
        """
        compute_hints() -> None

        compute hints - main hint intelligence
        """
        rows = self._stacks['set']['rows']
        for i in range(len(rows) - 3):
            left, right = rows[i], rows[i + 1]
            if left.cards and right.cards:
                if right.cardsBuild(left, left.cards[:1]):
                    self.add_hint(5000, 1, left, right)
                elif left.cardsBuild(right, right.cards[:1]):
                    self.add_hint(5000, 1, right, left)
            left, right = rows[i], rows[i + 3]
            if left.cards and right.cards:
                if right.cardsBuild(left, left.cards[:1]):
                    self.add_hint(6000, 1, left, right)
                elif left.cardsBuild(right, right.cards[:1]):
                    self.add_hint(6000, 1, right, left)


class PushPin_Talon(stack.DealRowTalon):
    """
    Deal just 1 additional card on _row_. _BOTTOM_IMAGE_ is None.
    """
    _BOTTOM_IMAGE_ = None

    @property
    def _piles(self):
        """
        _piles -> tup

        :return: deal Game.s.rows
        :rtype: tuple
        """
        for pile in (s for s in self.game.s['rows'] if not s.cards):
            return [pile]


class Bayan_RowStack(pairing.PairDropMethod, stack.OpenStack):
    """
    Build none. Play top. Drop cards of same rank or same suit when none or 2
    cards between on _foundation_. _BOTTOM_IMAGE_ is blank.
    """
    _BOTTOM_IMAGE_ = 'blank'

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        Sequence is same rank or same suit.

        :param sequence cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean
        """
        return (len(cards) == 2 and
                cmp_cards.isSameRank(cards) or cmp_cards.isSameSuit(cards))

    def _cardPairing(self, *args):
        """
        _cardPairing(*args) -> bool

        :param Stack left: neighbor pile left.
        :param Stack right: neighbor pile right.
        :return: cards to compare when distance correct.
        """
        if len(args) == 2 and not abs(args[0].id - args[1].id) in (1, 2):
            return False
        return self._sequence([s.TOPCARD for s in args])

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check parent *capabilities* with cards. Cards from 1st or 3rd off W e
        this pile and same rank or same suit.

        :param object from_stack: pile playing cards.
        :param tuple cards: cards playing on pile.
        :return: pile can build with cards from played from_stack?
        :rtype: boolean

        seealso:
          Method pysollib.stackcaps.builds for the *capability* check.
        """
        return (self._cardPairing(self, from_stack) and
                super(Bayan_RowStack, self).cardsBuild(from_stack, cards))

    @state.move_state('fill')
    def _dropPairMove(self, ncards, stack, **kw):
        """
        _dropPairMove(ncards:int, stack:obj [, frames:int=-1, shadow:int=-1])
          -> None

        Drop stack of pair to _foundation_ with sound.

        :param integer ncards: number of cards from top of pile.
        :param object stack: receiving pile.
        """
        game = self.game
        game._move_do(a_move.AUpdateStackMove(game.s['talon'], c.UNDO)) # low level
        super(Bayan_RowStack, self)._dropPairMove(ncards, stack, **kw)
        game._move_do(a_move.AUpdateStackMove(game.s['talon'], c.REDO))  # low level


class PushPin_RowStack(pairing.PairDropMethod, stack.OpenStack):
    """
    Build none. Play top. Drop cards between pairing of same rank or same suit
    when 1 or 2 cards between on _foundation_. _BOTTOM_IMAGE_ is blank.
    """

    def _cardPairing(self, *args):
        """
        _cardPairing(*args) -> bool

        :param Stack left: neighbor pile left.
        :param Stack right: neighbor pile right.
        :return: cards to compare when distance correct.
        """
        if len(args) == 2 and not abs(args[0].id - args[1].id) in (1,):
            return []
        if len(args) == 1:
            left = args[0].id - 1
            right = args[0].id + 1
        else:
            left = min(s.id for s in args) - 1
            right = max(s.id for s in args) + 1
        if left < 0 or right > 51:
            return []
        rows = self.game.allstacks
        return self._sequence([rows[i].cards[-1:] for i in (left, right,)])

    def clickHandler(self, event):
        """
        clickHandler(event:obj) -> bool

        Override to drop TOPCARD on foundation if _checkPair(-1, +1).

        :param object event: canvas event.
        :return: event state
        :rtype: True | False | None
        """
        game = self.game
        if self._checkPair(self):
            game.move(1, self, game.s['foundations'][0])
            return True
        return False


class Accordion_RowStack(Bayan_RowStack):
    """
    Build none. Play top. Drop cards of same rank or same suit when none or 2
    cards between on _foundation_with from_stack right of to_stack.
    _BOTTOM_IMAGE_ is blank.
    """

    def _cardPairing(self, *args):
        """
        _cardPairing(*args) -> bool

        :param Stack left: neighbor pile left.
        :param Stack right: neighbor pile right.
        :return: cards to compare when distance correct.
        """
        return (super(Accordion_RowStack, self)._cardPairing(*args) and
                args[0].id < args[1].id)


class HandSolitaire_RowStack(Bayan_RowStack):
    """
    Build none. Play top. Drop cards of same suit when 2 cards between or 2
    cards between when of same rank on _foundation_. _BOTTOM_IMAGE_ is blank.
    """

    def _cardPairing(self, *args):
        """
        _cardPairing(*args) -> bool

        :param Stack left: neighbor pile left.
        :param Stack right: neighbor pile right.
        :return: cards to compare when distance correct.
        """
        if len(args) != 2:
            return []
        if abs(args[0].id - args[1].id) in (1,):
            left = args[0].id - 1
            right = args[1].id + 1
            if left < 0 or right > 51:
                return []
            rows = self.game.allstacks
            return cmp_cards.isSameRank(rows[left].cards[-1:] +
                                        rows[right].cards[-1:])
        if abs(args[0].id - args[1].id) in (3,):
            return cmp_cards.isSameSuit([s.cards[-1:] for s in args])


class PushPin(game.Game):
    """
    Drop 50 cards on _foundation_ within a turn.

    _row_
      Pile 52 over 6 rows E _talon_. Init deal 1 card 1st 3 pile. Drop up to 2
      cards between pair by same rank or same suit on _foundation_. Click
      drop 1 and play on neighbor drop 2. Play top. Gap move cards left else
      none.

    _talon_
      Pile 1 NW. Deal 1st empty _row_ pile.

    _foundation_
      Pile 1 horz SW _row_. Drop up to 2 cards between pair from _row_. Play
      none.

    note::
      ⟪autoflip⟫, ⟪autodrop⟫, ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: PushPin_Talon
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: PushPin_RowStack
    :cvar Hint _hint_: PushPin_Hint
    """
    __info__ = dict(id=287, name='Push Pin', game_type=GT.PAIRING,
                    decks=1, redeals=0, skill_level=SL.MOSTLY_LUCK,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = PushPin_Talon
    _foundation_ = stack.SingleFoundation
    _row_ = PushPin_RowStack
    _hint_ = PushPin_Hint

    def _create_game(self, **kw):
        l, s = Layout(self), self.s
        dx, dy = 9, 6
        self.table_size = (l.XM + dx * l.XS, l.YM + dy * l.YS,)
        for i in range(dy):
            for j in range(dx):
                n = j + dx * i
                if n < 1:
                    continue
                if n > l.NCARDS:
                    break
                x, y = l.delta_coords(dx=j if i % 2 == 0 else dx - j - 1, dy=i)
                s['rows'].append(self._row_(x, y, game=self))
        x, y = l.XM, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        y = self.height - l.YS
        x = self.width - l.XS
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.defaultStackGroups()
        return l

    def _start(self, rows=3, **kw):
        """
        Deal initial cards for new game.
          - s[rows][:ROWS] 1 ea up.

        .. \_start([rows=3]) -> None
        """
        self.s['talon'].dealRow(rows=self.s.rows[:rows])

    @state.move_state('fill')
    def _auto_fill(self, stack):
        """
        Move stack left when _row_ has mid gap.

        *Parameters*:
           stack : Stack
             pile just played
        """
        if not self.can_play_sound:
            self._play_sound('deal')
        while True:
            to_stack = None
            for row in self.s['rows']:
                if not row.cards:
                    to_stack = row
                if to_stack is not None and row.cards:
                    row.move_pile(1, to_stack, frames=2, shadow=0)
                    break
            else:
                break

    @property
    def is_complete(self):
        foundation = self.s['foundations'][0]
        return len(foundation.cards) == foundation.cap['max_cards'] - 2

    def _autopiles(self, event=None):
        """
        Return piles for auto or quick move.

        Overide to disable autoflip, autodrop, quickplay.

        .. \_autopiles(self, event=None) -> seq(seq(Pile ...), seq(Pile ...),
             seq(Pile ...))
        """
        return autostacks.none()


class RoyalMarriage(PushPin):
    """
    Drop 50 cards on _foundation_ within a turn.

    _row_
      Pile 52 over 6 rows E _talon_. Init deal base QUEEN hearts and 2 card on
      1st 3 pile. Drop up to 2 cards between pair by same rank or same suit on
      _foundation_. Click drop 1 and play on neighbor drop 2. Play top. Gap
      move cards left else none. KING hearts is last card played.

    _talon_
      Pile 1 NW. Deal 1st empty _row_ pile.

    _foundation_
      Pile 1 horz SW _row_. Drop up to 2 cards between pair from _row_. Play
      none.

    note::
      ⟪autoflip⟫, ⟪autodrop⟫, ⟪quickplay⟫ disabled.

    seealso::
      <http://en.wikipedia.org/wiki/Royal_Marriage>
    ------
    :cvar Stack _talon_: PushPin_Talon
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: PushPin_RowStack
    :cvar Hint _hint_: PushPin_Hint
    """
    __info__ = dict(id=288, name='Royal Marriage', game_type=GT.PAIRING,
                    decks=1, redeals=0, skill_level=SL.MOSTLY_LUCK,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _pack_place(self, cards):
        """
        _pack_place(cards:seq(Card ...)) -> seq(Card ...)

        Overide to return QUEEN heart on top and KING heart on bottom for _row_
        play.
        """
        cards = place.MovetoBottom(cards,
                                     lambda c: place.AllKing(c, suits=[2]),
                                     ncards=1)
        return place.MovetoTop(cards,
                                 lambda c: place.AllQueen(c, suits=[2]),
                                 ncards=1)


class Queens(PushPin):
    """
    Drop 50 cards on _foundation_ within a turn.

    _row_
      Pile 52 over 6 rows E _talon_. Init deal 52 card. Drop up to 2 cards
      between pair by same rank or same suit on _foundation_. Click drop 1 and
      play on neighbor drop 2. Play top. Gap move cards left else none.

    _talon_
      Pile 1 NW.

    _foundation_
      Pile 1 horz SW _row_. Drop up to 2 cards between pair from _row_. Play
      none.

    note::
      ⟪autoflip⟫, ⟪autodrop⟫, ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: PushPin_Talon
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: PushPin_RowStack
    :cvar Hint _hint_: PushPin_Hint
    """
    __info__ = dict(id=303, name='Queens', game_type=(GT.PAIRING |
                    GT.OPEN | GT.HIDDEN), decks=1, redeals=0,
                    version='0.70up')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[rows] 1 ea up.

        .. \_start([]) -> None
        """
        self.s['talon'].dealRow()


class Accordion(PushPin):
    """
    Drop 52 cards on _foundation_ within a turn.

    _row_
      Pile 52 over 6 rows E _talon_. Init deal 1 card on 1st 2 pile. Drop pair
      same rank or same suit with up to 2 cards between on _foundation_. Play
      top. Gap move cards left else none.

    _talon_
      Pile 1 NW. Deal 1st empty _row_ pile.

    _foundation_
      Pile 1 horz SW _row_. Drop pair with up to 2 cards between from _row_.
      Play none.

    note::
      ⟪autoflip⟫, ⟪autodrop⟫, ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: PushPin_Talon
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: Bayan_RowStack
    :cvar Hint _hint_: Accordion_Hint
    """
    __info__ = dict(id=656, name='Bayan', game_type=GT.PAIRING, decks=1,
                    redeals=0, skill_level=SL.BALANCED, version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70
    _hint_ = Accordion_Hint
    _row_ = Bayan_RowStack

    def _start(self, rows=2, **kw):
        """
        Deal initial cards for new game.
          - s[rows][:ROWS] 1 ea up.

        .. \_start([rows=2]) -> None
        """
        super(Accordion, self)._start(rows=rows, **kw)

    @property
    def is_complete(self):
        foundation = self.s['foundations'][0]
        return len(foundation.cards) == foundation.cap['max_cards']


class Accordion2(Accordion):
    """
    Drop 51 cards on _foundation_ within a turn.

    _row_
      Pile 52 over 6 rows E _talon_. Init deal 1 card on 1st 2 pile. Drop pair
      same rank or same suit with up to 2 cards between from E on
      _foundation_. Play top. Gap move cards left else none.

    _talon_
      Pile 1 NW. Deal 1st empty _row_ pile.

    _foundation_
      Pile 1 horz SW _row_. Drop pair with up to 2 cards between from _row_.
      Play none.

    note::
      ⟪auto-⟫ disabled.

    seealso::
      <http://en.wikipedia.org/wiki/Accordion_%28solitaire%29>
    ------
    :cvar Stack _talon_: PushPin_Talon
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: Accordion_RowStack
    :cvar Hint _hint_: Accordion_Hint
    """
    __info__ = dict(id=772, name='Accordion', game_type=GT.PAIRING,
                    decks=1, redeals=0, skill_level=SL.BALANCED,
                    altnames=('Idle Year', 'Methuselah', 'Tower of Babel',),
                    version='0.70up')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _row_ = Accordion_RowStack

    @property
    def is_complete(self):
        foundation = self.s['foundations'][0]
        return len(foundation.cards) == foundation.cap['max_cards'] - 1


class RelaxedAccordion(Accordion2):
    """
    Drop 51 cards on _foundation_ within a turn.

    _row_
      Pile 52 over 6 rows E _talon_. Init deal 1 card on 1st 2 pile. Drop pair
      same rank or same suit with up to 2 cards between on _foundation_. Play
      top. Gap move cards left else none.

    _talon_
      Pile 1 NW. Deal 1st empty _row_ pile.

    _foundation_
      Pile 1 horz SW _row_. Drop pair with up to 2 cards between from _row_.
      Play none.

    note::
      ⟪auto-⟫ disabled.
    ------
    :cvar Stack _talon_: PushPin_Talon
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: Bayan_RowStack
    :cvar Hint _hint_: Accordion_Hint
    """
    __info__ = dict(id=773, name='Relaxed Accordion',
                    game_type=GT.PAIRING, decks=1, redeals=0,
                    skill_level=SL.BALANCED, version='0.70up')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _row_ = Bayan_RowStack


class HandSolitaire(Accordion):
    """
    Drop 52 cards on _foundation_ within a turn.

    _row_
      Pile 52 over 6 rows E _talon_. Init deal 1 card on 1st 2 pile. Drop
      pair with 2 cards between of same suit or 2 cards between pair of same
      rank on _foundation_. Play top. Gap move cards left else none.

    _talon_
      Pile 1 NW. Deal 1st empty _row_ pile.

    _foundation_
      Pile 1 horz SW _row_. Drop pair with up to 2 cards between from _row_.
      Play none.

    note::
      ⟪auto-⟫ disabled.
    ------
    :cvar Stack _talon_: PushPin_Talon
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: HandSolitaire_RowStack
    :cvar Hint _hint_: Accordion_Hint
    """
    __info__ = dict(id=774, name='Hand Solitaire', game_type=GT.PAIRING,
                    decks=1, redeals=0, skill_level=SL.BALANCED,
                    version='0.70up')
    __author__ = 'Jeremy AustinBardo'
    __version__ = 7.70

    _row_ = HandSolitaire_RowStack


PROVIDES = [PushPin, RoyalMarriage, Accordion, Accordion2, RelaxedAccordion,
            Queens, HandSolitaire]